package com.xyy.saas.payment.adpater.impl;

import com.alibaba.fastjson.JSON;
import com.xyy.framework.redis.core.RedisClient;
import com.xyy.saas.payment.adpater.PaymentAdapter;
import com.xyy.saas.payment.adpater.config.BankConfigManager;
import com.xyy.saas.payment.cashier.core.vo.ChannelLoginVO;
import com.xyy.saas.payment.channel.login.core.bo.ChannelLoginResultBO;
import com.xyy.saas.payment.common.Constants;
import com.xyy.saas.payment.common.RocketMqTopic;
import com.xyy.saas.payment.common.constants.MqConstant;
import com.xyy.saas.payment.common.exception.PaymentException;
import com.xyy.saas.payment.config.MiniCashierPayConfig;
import com.xyy.saas.payment.config.YkqWxConfig;
import com.xyy.saas.payment.cores.bo.AppPayBo;
import com.xyy.saas.payment.cores.bo.MiniPayBo;
import com.xyy.saas.payment.cores.bo.PrepayBo;
import com.xyy.saas.payment.cores.bo.QueryResultBo;
import com.xyy.saas.payment.cores.enums.BusinessOrderTypeEnum;
import com.xyy.saas.payment.cores.enums.ResultCodeEnum;
import com.xyy.saas.payment.dao.mapper.RefundOrderMapper;
import com.xyy.saas.payment.dao.model.PayResultOrder;
import com.xyy.saas.payment.dao.model.PrePayOrder;
import com.xyy.saas.payment.dao.model.RefundOrder;
import com.xyy.saas.payment.dao.model.ThirdUserInfo;
import com.xyy.saas.payment.payment.core.bo.RefundBO;
import com.xyy.saas.payment.payment.core.dto.AppPayDto;
import com.xyy.saas.payment.payment.core.dto.MiniPayDto;
import com.xyy.saas.payment.payment.core.dto.PrepayDto;
import com.xyy.saas.payment.payment.core.dto.RefundDto;
import com.xyy.saas.payment.payment.core.service.PrePayOrderService;
import com.xyy.saas.payment.service.ThirdUserInfoService;
import com.xyy.saas.payment.util.*;
import com.xyy.saas.payment.util.mq.MqProducer;
import com.xyy.util.Base64Util;
import com.xyy.util.StringUtil;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;


/**
 * @Author: wuqiongda
 * @Date: 2019/12/16 11:17
 */
@Service
public class WxAdapter implements PaymentAdapter {
    @Autowired
    private RefundOrderMapper refundOrderMapper;
    @Autowired
    private MqProducer mqProducer;
    @Autowired
    private PrePayOrderService prePayOrderService;
    @Autowired
    private RedisClient redisClient;
    @Autowired
    private YkqWxConfig ykqWxConfig;
    @Autowired
    private MiniCashierPayConfig miniCashierPayConfig;
    @Resource
    private ThirdUserInfoService thirdUserInfoService;
    @Autowired
    private BankConfigManager bankConfigManager;

    private static final Logger LOGGER = LoggerFactory.getLogger(WxAdapter.class);

    @Override
    public ChannelLoginResultBO login(ChannelLoginVO channelLoginVO) throws PaymentException {
        String loginUrl = "https://api.weixin.qq.com/sns/jscode2session";
        ThirdUserInfo thirdUserInfo = thirdUserInfoService.getThirdUserInfoByUserId(channelLoginVO.getAccountId());
        String appId = miniCashierPayConfig.getWxAppId();
        String secret = miniCashierPayConfig.getWxSecret();

        if(thirdUserInfo != null) {
            if(Constants.PINGAN.equals(thirdUserInfo.getThirdType())) {
                appId = ApolloUtil.getProperty("pingan_mini_wx_appid");
                secret = ApolloUtil.getProperty("pingan_mini_wx_secret");
            }
            else if(Constants.QIFUTONG.equals(thirdUserInfo.getThirdType())) {
                appId = ApolloUtil.getProperty("fbank_mini_wx_appid");
                secret = ApolloUtil.getProperty("fbank_mini_wx_secret");
            }
        }
        // 大商户
        else if(Boolean.parseBoolean(ApolloUtil.getProperty(Constants.EC_POP_BIG_SHOP_TO_PINGAN_SWITCH, Boolean.FALSE.toString())) &&
                Objects.equals(ApolloUtil.getProperty(Constants.POP_DIRECT_PAY_YBM_TECH_USER_ID), channelLoginVO.getAccountId()))
        {
            appId = ApolloUtil.getProperty("pingan_mini_wx_appid");
            secret = ApolloUtil.getProperty("pingan_mini_wx_secret");
        }
        else if(bankConfigManager.isFuminBankAggregatePay(channelLoginVO.getBusinessOrderType())) {
            appId = ApolloUtil.getProperty("fbank_mini_wx_appid");
            secret = ApolloUtil.getProperty("fbank_mini_wx_secret");
        }

        ChannelLoginResultBO bo = new ChannelLoginResultBO();
        String url = String.format(loginUrl + "?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code",
                appId, secret, channelLoginVO.getAuthCode());
        try {
            String response = HttpClientUtil.xyyHttpGetRequest(url);

            if(response == null) {
                LOGGER.error("WxAdapter#login failed url:{} ", url);
                throw new PaymentException(ResultCodeEnum.CHANNEL_CONNECT_ERROR);
            }

            Map<String, String> jsonRes = JSONUtils.parse2Map(response, String.class, String.class);

            if(jsonRes == null) {
                LOGGER.error("WxAdapter#login parse response error url:{} response:{}", url, response);
                throw new PaymentException(ResultCodeEnum.CHANNEL_CONNECT_ERROR);
            }

            LOGGER.info("WxAdapter#login url:{} result:{}", url, jsonRes);
            bo.setChannelUserId(jsonRes.get("openid"));
            return bo;
        }
        catch(Exception e) {
            LOGGER.error("WxAdapter#login error url:{}", url, e);
            throw new PaymentException(ResultCodeEnum.CHANNEL_CONNECT_ERROR);
        }
    }

    @Override
    public PrepayBo prepay(PrepayDto prepayDto) throws PaymentException {
        return prePayOrderService.prepayOrderByPrepayDto(prepayDto);
    }

    @Override
    public RefundBO refund(RefundDto refundDto) throws PaymentException {
        RefundBO refundBO = new RefundBO();
        SortedMap<String, String> resultMap;
        Map<String, Object> packageParams = new TreeMap<>(
                new Comparator<String>() {
                    @Override
                    public int compare(String obj1, String obj2) {
                        // 升序排序
                        return obj1.compareTo(obj2);
                    }
                });
        try {
            boolean isNewMerchant = false;
            packageParams.put("appid", ApolloUtil.getProperty(refundDto.getBusinessOrderType()+".wx.official.appid"));
            if (StringUtil.isEmpty(refundDto.getMerchantId())){
                packageParams.put("mch_id", "1566654551");
            }else {
                packageParams.put("mch_id", refundDto.getMerchantId());
            }
            packageParams.put("nonce_str", WxUtil.getNonceStr());
            packageParams.put("transaction_id", refundDto.getChannelChargeId());
            packageParams.put("out_refund_no", refundDto.getRefundNo());
            packageParams.put("total_fee", refundDto.getTotalAmount());
            packageParams.put("refund_fee", refundDto.getAmount());
            packageParams.put("refund_desc", refundDto.getDescription());
            packageParams.put("notify_url", ApolloUtil.getProperty("wx.notifyUrl.refund"));
            if (BusinessOrderTypeEnum.YIKUAIQIAN_DOC.getType().equals(refundDto.getBusinessOrderType())){
                if (ApolloUtil.getProperty(refundDto.getBusinessOrderType()+".wx.newMchid").equals(refundDto.getMerchantId())){
                    isNewMerchant = true;
                }
            }
            boolean refundAccountSwitch = Boolean.parseBoolean(ApolloUtil.getProperty("wx.oldMchid.refundAccountSwitch"));
            if(refundAccountSwitch) {
                if(packageParams.get("mch_id").equals("1566654551")) {
                    String refundAccount = ApolloUtil.getProperty("wx.oldMchid.refundAccount");
                    packageParams.put("refund_account", refundAccount);
                }
            }
            String xml = WxUtil.map2Xml(packageParams, refundDto.getBusinessOrderType(), isNewMerchant);
            SSLConnectionSocketFactory sslsf = WxUtil.initKeyStore(refundDto.getMerchantId());
            CloseableHttpClient httpclient = HttpClients.custom()
                    .setSSLSocketFactory(sslsf)
                    .build();
            HttpPost httpPost = new HttpPost(ApolloUtil.getProperty("wx.pay.refundUrl"));
            httpPost.setEntity(new StringEntity(xml, "UTF-8"));
            CloseableHttpResponse response = httpclient.execute(httpPost);

            String resultStr = "";
            if (response != null && response.getEntity() != null) {
                resultStr = EntityUtils.toString(response.getEntity(), "UTF-8");
                response.close();
            }
            httpclient.close();
            resultMap = WxUtil.doXMLParse(resultStr);
            LOGGER.info("WxAdapter#refund result pay_no:{},result:{}", refundDto.getPayNo(), JSON.toJSONString(resultMap));
        } catch (Exception e) {
            LOGGER.error("WxAdapter refund error refundDto=" + JSON.toJSON(refundDto), e);
            throw new PaymentException(ResultCodeEnum.DEFAULT_ERROR);
        }
        if ("SUCCESS".equals(resultMap.get("return_code")) && "SUCCESS".equals(resultMap.get("result_code"))) {
            refundBO.setRefundNo(refundDto.getRefundNo());
            refundBO.setChannelRefundNo(resultMap.get("refund_id"));
            refundBO.setStatus(RefundOrder.STATUS_IN);
            LOGGER.info("WxAdapter#refund create success pay_no:{},result:{}", refundDto.getPayNo(), JSON.toJSONString(resultMap));
        } else if ("NOTENOUGH".equals(resultMap.get("return_code")) || "NOTENOUGH".equals(resultMap.get("result_code"))) {
            throw new PaymentException(ResultCodeEnum.CHANNEL_TODAY_MONEY_NOT_ENOUGH);
        } else {
            LOGGER.error("WxAdapter#refund create failed refundDto:{},result:{}", JSON.toJSONString(refundDto), JSON.toJSONString(resultMap));
            throw new PaymentException(ResultCodeEnum.CHANNEL_CONNECT_ERROR);
        }
        return refundBO;
    }

    @Override
    public QueryResultBo queryPayStatus(String tradeNo, PrePayOrder prePayOrder) throws PaymentException {
        QueryResultBo checkResultBO = new QueryResultBo();
        try {
            Map<String, Object> reqMap = new TreeMap<String, Object>(
                    new Comparator<String>() {
                        @Override
                        public int compare(String obj1, String obj2) {
                            // 升序排序
                            return obj1.compareTo(obj2);
                        }
                    });
            reqMap.put("appid", ykqWxConfig.getWxYikuaiqianDocOfficialAppId());
            reqMap.put("mch_id", ykqWxConfig.getWxYikuaiqianDocMchId());
            reqMap.put("out_trade_no", tradeNo);
            reqMap.put("nonce_str", WxUtil.getNonceStr());
            //接入新业务方需要修改
            String reqStr = WxUtil.map2Xml(reqMap, "yikuaiqian_doc",false);
            String wxUrl = ykqWxConfig.getWxPayQueryOrder();
            //(String url, String json, String contentType)
            String resultXml = HttpClientUtil.xyyHttpPostRequest(wxUrl, reqStr, "application/xml");
            LOGGER.info("WxAdapter queryPayStatus wx return result resultXml:{}", resultXml);
            Map<String, String> resultParamsMap = WxUtil.doXMLParse(resultXml);
            if (resultParamsMap.get("return_code") == null || !"SUCCESS".equals(resultParamsMap.get("return_code"))
                    || resultParamsMap.get("result_code") == null || !"SUCCESS".equals(resultParamsMap.get("result_code"))) {
                LOGGER.error("WxAdapter queryPayStatus wechat post error tradeNo:{}, msg:{}", tradeNo, resultParamsMap.get("return_msg"));
                throw new PaymentException(ResultCodeEnum.THIRD_PARAM_ERROR);
            }
            String state = resultParamsMap.get("trade_state");
            String transactionId = resultParamsMap.get("transaction_id");
            checkResultBO.setChannelChargeNo(transactionId);
            if ("SUCCESS".equals(state)) {
                //通知业务方
                checkResultBO.setStatus("FINISHED");
                return checkResultBO;
            }
            if ("REFUND".equals(state) || "CLOSED".equals(state) || "REVOKED".equals(state) || "PAYERROR".equals(state)) {
                //订单已经是最终态，什么都不做
                checkResultBO.setStatus("FAILED");
                return checkResultBO;
            }
            if ("NOTPAY".equals(state) || "USERPAYING".equals(state)) {
                //重试
                checkResultBO.setStatus("PROCESSING");
                return checkResultBO;
            }

        } catch (Exception e) {
            LOGGER.error("WxAdapter#queryPayStatus error tradeNo:{}", tradeNo, e);
            throw new PaymentException(ResultCodeEnum.CHANNEL_CONNECT_ERROR.getCode(), ResultCodeEnum.CHANNEL_CONNECT_ERROR.getMsg());
        }
        return checkResultBO;
    }

    @Override
    public QueryResultBo queryRefundStatus(RefundOrder refundOrder) throws PaymentException {
        Map<String, Object> packageParams = new TreeMap<>(
                new Comparator<String>() {
                    @Override
                    public int compare(String obj1, String obj2) {
                        // 升序排序
                        return obj1.compareTo(obj2);
                    }
                });
        try {
            packageParams.put("appid", ykqWxConfig.getWxYikuaiqianDocOfficialAppId());
            packageParams.put("mch_id", ykqWxConfig.getWxYikuaiqianDocMchId());
            packageParams.put("nonce_str", WxUtil.getNonceStr());
            packageParams.put("refund_id", refundOrder.getChannelRefundNo());
            //接入新的业务方需要修改 最后的参数写死了
            String XML = WxUtil.map2Xml(packageParams,"yikuaiqian_doc", false);
            String wxUrl = ykqWxConfig.getWxPayQueryRefund();
            String resultXml = HttpClientUtil.xyyHttpPostRequest(wxUrl, XML, "application/xml");
            LOGGER.info("WxAdapter queryRefundStatus wx return result resultXml:{}", resultXml);
            Map<String, String> resultParamsMap = null;
            resultParamsMap = WxUtil.doXMLParse(resultXml);
            //解析微信请求结果，如果成功返回给前端
            if (resultParamsMap.get("return_code") == null || !"SUCCESS".equals(resultParamsMap.get("return_code"))
                    || resultParamsMap.get("result_code") == null || !"SUCCESS".equals(resultParamsMap.get("result_code"))) {
                LOGGER.error("WxAdapter queryRefundStatus wechat post error refund_id:{}, message:{}", refundOrder.getChannelRefundNo(), JSON.toJSONString(resultParamsMap));
                throw new PaymentException(ResultCodeEnum.THIRD_PARAM_ERROR);
            }
            QueryResultBo checkResultBO = new QueryResultBo();
            if ("SUCCESS".equals(resultParamsMap.get("refund_status_0"))) {
                checkResultBO.setStatus("FINISHED");
                checkResultBO.setAmount(refundOrder.getAmount());
                checkResultBO.setFee(0);
                checkResultBO.setSuccessTime(DateUtil.sdf1ToDate(resultParamsMap.get("refund_success_time_0")).getTime() / 1000);
                return checkResultBO;
            }
            if ("FAIL".equals(resultParamsMap.get("refund_status_0")) || "CHANGE".equals(resultParamsMap.get("refund_status_0"))) {
                checkResultBO.setStatus("FAILED");
                checkResultBO.setAmount(refundOrder.getAmount());
                checkResultBO.setFee(0);
                checkResultBO.setSuccessTime(Constants.DEFAULT_UNIX_TIME);
                return checkResultBO;
            }
            if ("PROCESSING".equals(resultParamsMap.get("refund_status_0"))) {
                checkResultBO.setStatus("PROCESSING");
                checkResultBO.setAmount(refundOrder.getAmount());
                checkResultBO.setFee(0);
                checkResultBO.setSuccessTime(Constants.DEFAULT_UNIX_TIME);
                return checkResultBO;
            }
        } catch (Exception e) {
            LOGGER.error("WxAdapter#queryRefundStatus error refundOrder:{}", JSON.toJSONString(refundOrder), e);
        }
        throw new PaymentException(ResultCodeEnum.DEFAULT_ERROR);
    }

    @Override
    public AppPayBo appPay(AppPayDto appPayDto) throws PaymentException {
        AppPayBo appPayBo = new AppPayBo();
        try {
            Map<String, Object> reqMap = new TreeMap<String, Object>(
                    new Comparator<String>() {
                        @Override
                        public int compare(String obj1, String obj2) {
                            // 升序排序
                            return obj1.compareTo(obj2);
                        }
                    });
            LOGGER.info("WxAdapter appPay begin params:{}", JSONUtils.toJSON(appPayDto));
            boolean isNewMerchant = isSelectMerchant(appPayDto.getPayNo());
            if (isNewMerchant){
                reqMap.put("mch_id", ApolloUtil.getProperty(appPayDto.getBusinessOrderType()+".wx.newMchid"));
            }else {
                reqMap.put("mch_id", ApolloUtil.getProperty(appPayDto.getBusinessOrderType()+".wx.mchid"));
            }
            reqMap.put("appid", ApolloUtil.getProperty(appPayDto.getBusinessOrderType()+".wx.app.appid"));
            reqMap.put("nonce_str", WxUtil.getNonceStr());
            reqMap.put("body", appPayDto.getBody());
            reqMap.put("out_trade_no", appPayDto.getPayNo());
            //订单总金额，单位为分
            reqMap.put("total_fee", "" + appPayDto.getAmount());
            //用户端ip
            reqMap.put("spbill_create_ip", "192.168.1.75");
            //通知地址
            reqMap.put("notify_url", ApolloUtil.getProperty("wx.notifyUrl.pay"));
            reqMap.put("trade_type", "APP");
            if (appPayDto.getMap() != null && appPayDto.getMap().containsKey("timeExpire")) {
                reqMap.put("time_expire", appPayDto.getMap().get("timeExpire"));
            }
            Map<String, String> attachMap = new HashMap<>();
            attachMap.put("callback_address", appPayDto.getCallbackAddress());
            attachMap.put("business_type", appPayDto.getBusinessType());
            attachMap.put("business_order_type", appPayDto.getBusinessOrderType());
            attachMap.put("receiver_id", appPayDto.getReceiverId());
            //透传参数
            reqMap.put("attach", JSONUtils.toJSON(attachMap));
            //构建XML
            String reqStr = WxUtil.map2Xml(reqMap, appPayDto.getBusinessOrderType(), isNewMerchant);
            String wxUrl = ApolloUtil.getProperty("wx.pay.appUrl");

            //(String url, String json, String contentType)
            String resultXml = HttpClientUtil.xyyHttpPostRequest(wxUrl, reqStr, "application/xml");
            LOGGER.info("WxAdapter appPay wx return result resultXml:{}", resultXml);
            Map<String, String> resultParamsMap = WxUtil.doXMLParse(resultXml);
            //解析微信请求结果，如果成功返回给前端
            if (resultParamsMap.get("return_code") == null || !"SUCCESS".equals(resultParamsMap.get("return_code"))
                    || resultParamsMap.get("result_code") == null || !"SUCCESS".equals(resultParamsMap.get("result_code"))) {
                LOGGER.error("WxAdapter appPay wechat post error payNo:{}, message:{}", appPayDto.getPayNo(), JSONUtils.toJSON(resultParamsMap));
                throw new PaymentException(ResultCodeEnum.THIRD_PARAM_ERROR);
            }
            Map<String, Object> resultMap = new TreeMap<>(
                    new Comparator<String>() {
                        @Override
                        public int compare(String obj1, String obj2) {
                            // 升序排序
                            return obj1.compareTo(obj2);
                        }
                    });
            if (StringUtil.isEmpty(resultParamsMap.get("prepay_id"))) {
                String errCode = resultParamsMap.get("err_code");
                String errCodeDes = resultParamsMap.get("err_code_des");
                LOGGER.error("WxAdapter appPay Failed to call wechat unified order interface err_code：{},err_code_des:{}", errCode, errCodeDes);
                throw new PaymentException(ResultCodeEnum.THIRD_PARAM_ERROR.getCode(), errCodeDes);
            }

            resultMap.put("appid", ApolloUtil.getProperty(appPayDto.getBusinessOrderType()+".wx.app.appid"));
            if (isSelectMerchant(appPayDto.getPayNo())){
                reqMap.put("partnerid", ApolloUtil.getProperty(appPayDto.getBusinessOrderType()+".wx.newMchid"));
            }else {
                reqMap.put("partnerid", ApolloUtil.getProperty(appPayDto.getBusinessOrderType()+".wx.mchid"));
            }
            resultMap.put("prepayid", resultParamsMap.get("prepay_id"));
            resultMap.put("package", "Sign=WXPay");
            //解析随机字符串
            resultMap.put("noncestr", WxUtil.getNonceStr());
            //时间戳
            resultMap.put("timestamp", String.valueOf((System.currentTimeMillis() / 1000)));
            //返回一个json给前端
            resultMap.put("sign", WxUtil.generateSign(resultMap,appPayDto.getBusinessOrderType(), isNewMerchant));
            resultMap.put("package", "Sign=WXPay");
            appPayBo.setPayLoad(JSONUtils.toJSON(resultMap));
            queryPayState(appPayDto.getPayNo(), appPayDto.getBusinessOrderType(), "app");
            LOGGER.info("WxAdapter appPay Call wechat unified order interface succeeded orderCid:{},result:{}", appPayDto.getPayNo(), appPayBo.getPayLoad());
        } catch (Exception e) {
            LOGGER.error("WxAdapter appPay Wechat app unified order failed appPayDto:{}", JSON.toJSON(appPayDto), e);
            throw new PaymentException(ResultCodeEnum.CHANNEL_CONNECT_ERROR.getCode(), ResultCodeEnum.CHANNEL_CONNECT_ERROR.getMsg());
        }
        redisClient.set(appPayDto.getBusinessOrderType() + appPayDto.getPayNo() + "wxapp", appPayBo, 7200);
        return appPayBo;
    }

    @Override
    public MiniPayBo miniPay(MiniPayDto miniPayDto) throws PaymentException {
        MiniPayBo miniPayBo = new MiniPayBo();
        LOGGER.info("WxAdapter miniPay begin params:{}", JSONUtils.toJSON(miniPayDto));
        Map<String, Object> reqMap = new TreeMap<String, Object>(
                new Comparator<String>() {
                    @Override
                    public int compare(String obj1, String obj2) {
                        // 升序排序
                        return obj1.compareTo(obj2);
                    }
                });
        try {
            boolean isNewMerchant = isSelectMerchant(miniPayDto.getPayNo());
            if (isNewMerchant){
                reqMap.put("mch_id", ApolloUtil.getProperty(miniPayDto.getBusinessOrderType()+".wx.newMchid"));
            }else {
                reqMap.put("mch_id", ApolloUtil.getProperty(miniPayDto.getBusinessOrderType()+".wx.mchid"));
            }
            reqMap.put("appid", ApolloUtil.getProperty(miniPayDto.getBusinessOrderType()+".wx.mini.appid"));
            reqMap.put("nonce_str", WxUtil.getNonceStr());
            //业务方传
            reqMap.put("body", miniPayDto.getBody());
            reqMap.put("openid", miniPayDto.getMap().get("openId"));
            reqMap.put("out_trade_no", miniPayDto.getPayNo());
            //订单总金额，单位为分
            reqMap.put("total_fee", miniPayDto.getAmount());
            //用户端ip
            reqMap.put("spbill_create_ip", "192.168.1.75");
            reqMap.put("notify_url", ApolloUtil.getProperty("wx.notifyUrl.pay"));
            //trade_type=JSAPI时（小程序支付）
            reqMap.put("trade_type", "JSAPI");
            if (miniPayDto.getMap() != null && miniPayDto.getMap().containsKey("timeExpire")) {
                reqMap.put("time_expire", miniPayDto.getMap().get("timeExpire"));
            }

            Map<String, String> attachMap = new HashMap<>();
            attachMap.put("callback_address", miniPayDto.getCallbackAddress());
            attachMap.put("business_type", miniPayDto.getBusinessType());
            attachMap.put("business_order_type", miniPayDto.getBusinessOrderType());
            attachMap.put("receiver_id", miniPayDto.getReceiverId());
            reqMap.put("attach", JSONUtils.toJSON(attachMap));

            //构建XML
            String reqStr = WxUtil.map2Xml(reqMap, miniPayDto.getBusinessOrderType(), isNewMerchant);
            String wxUrl = ApolloUtil.getProperty("wx.pay.miniUrl");
            String resultXml = HttpClientUtil.xyyHttpPostRequest(wxUrl, reqStr, "application/xml");
            LOGGER.info("WxAdapter miniPay return result resultXml:{}", Base64Util.Bytes2Base64(resultXml.getBytes()));
            Map<String, String> resultParamsMap = WxUtil.doXMLParse(resultXml);

            //解析微信返回串 如果状态成功 则返回给前端
            //解析微信请求结果，如果成功返回给前端
            if (resultParamsMap.get("return_code") == null || !"SUCCESS".equals(resultParamsMap.get("return_code"))
                    || resultParamsMap.get("result_code") == null || !"SUCCESS".equals(resultParamsMap.get("result_code"))) {
                LOGGER.error("WxAdapter miniPay wechat post error payNo:{}, message:{}", miniPayDto.getPayNo(), JSONUtils.toJSON(resultParamsMap));
                throw new PaymentException(ResultCodeEnum.THIRD_PARAM_ERROR);
            }

            Map<String, Object> resultMap = new TreeMap<>(
                    new Comparator<String>() {
                        @Override
                        public int compare(String obj1, String obj2) {
                            // 升序排序
                            return obj1.compareTo(obj2);
                        }
                    });
            if (StringUtil.isEmpty(resultParamsMap.get("prepay_id"))) {
                String errCode = resultParamsMap.get("err_code");
                String errCodeDes = resultParamsMap.get("err_code_des");
                LOGGER.error("WxAdapter miniPay wechat post error payNo:{}, message:{}", miniPayDto.getPayNo(), JSONUtils.toJSON(resultParamsMap));
                throw new PaymentException(ResultCodeEnum.THIRD_PARAM_ERROR);
            }

            resultMap.put("appId", resultParamsMap.get("appid"));
            //解析随机字符串
            resultMap.put("nonceStr", WxUtil.getNonceStr());
            resultMap.put("package", "prepay_id=" + resultParamsMap.get("prepay_id"));
            resultMap.put("signType", "MD5");
            //时间戳
            resultMap.put("timeStamp", String.valueOf((System.currentTimeMillis() / 1000)));
            String paySign = WxUtil.generateSign(resultMap, miniPayDto.getBusinessOrderType(), isNewMerchant);
            resultMap.put("paySign", paySign);

            miniPayBo.setPayLoad(JSONUtils.toJSON(resultMap));
            queryPayState(miniPayDto.getPayNo(), miniPayDto.getBusinessOrderType(), "mini");
            LOGGER.info("WxAdapter miniPay Call wechat unified order interface succeeded orderCid:{},result:{}", miniPayDto.getPayNo(), miniPayBo.getPayLoad());
        } catch (Exception e) {
            LOGGER.error("WxAdapter miniPay Wechat Miniapp unified order failed appPayDto:{}", JSON.toJSON(miniPayDto), e);
            throw new PaymentException(ResultCodeEnum.CHANNEL_CONNECT_ERROR.getCode(), ResultCodeEnum.CHANNEL_CONNECT_ERROR.getMsg());
        }
        redisClient.set(miniPayDto.getBusinessOrderType() + miniPayDto.getPayNo() + "wxmini", miniPayBo, 7200);
        return miniPayBo;
    }

    /**
     * 退款重试
     * @param payResultOrder
     * @param refundOrder
     * @throws PaymentException
     */
    public void retryRefund(PayResultOrder payResultOrder, RefundOrder refundOrder) throws PaymentException {
        RefundDto refundDto = new RefundDto();
        refundDto.setChannelChargeId(payResultOrder.getChannelChargeNo());
        refundDto.setRefundNo(refundOrder.getRefundNo());
        refundDto.setTotalAmount(payResultOrder.getAmount());
        refundDto.setAmount(refundOrder.getAmount());
        refundDto.setDescription("订单退款");
        RefundBO refundBo = refund(refundDto);
        refundOrderMapper.updateStatusByRefundNo(refundOrder.getRefundNo(), refundBo.getChannelRefundNo(), Constants.DEFAULT_DATE, new Date(), refundBo.getStatus(), refundBo.getChannelTransactionNo(), refundOrder.getVersion());
    }


    /**
     * 查询订单的支付状态，只有在回调接口关闭的时候会开启此功能
     */
    private void queryPayState(String tradeNo, String businessOrderType, String payType) {
        boolean isQueryPayState = Boolean.parseBoolean(ApolloUtil.getProperty("isQueryPayState"));
        if (isQueryPayState) {
            LOGGER.info("WxAdapter queryPayState send query order state mq tradeNo:{}", tradeNo);
            Map<String, String> mqMsg = new HashMap<>(8);
            mqMsg.put("type", Constants.CHANNEL_WX_NAME);
            mqMsg.put("tradeNo", tradeNo);
            mqMsg.put("businessOrderType", businessOrderType);
            mqMsg.put("payType", payType);
            mqMsg.put("times", "0");
            mqProducer.syncSendDelayMsg(RocketMqTopic.TOPIC_QUERY_ORDER_STATE, JSON.toJSONString(mqMsg), MqConstant.DELAY_LEVELS[Integer.parseInt(ApolloUtil.getProperty("query.order.state.delay.level"))]);
        }
    }

    private boolean isSelectMerchant(String payNo){
        BigDecimal selectNum = new BigDecimal(ApolloUtil.getProperty("selectMerchantNum"));
        BigDecimal bPayNo = new BigDecimal(payNo);
        BigDecimal remainder = bPayNo.remainder(new BigDecimal(ApolloUtil.getProperty("grayscaleMax")));
        //如果余数小于设置的阈值 则放入新商户号 反之放入老商户号
        if (remainder.compareTo(selectNum)<0){
            return true;
        }else {
            return false;
        }
    }
}
